﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger;
using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;
using Weazel.Badger.Vhdl.Expressions;

namespace Weazel.Willows.InterfaceGeneration
{
	public class HardwareToSoftwareQueue
		: HardwareQueue
	{
		/// <summary>
		/// Get the (expected) name of the 'can_write' port on the hardware entity
		/// </summary>
		public string HardwareCanWritePortName
		{
		  get { return "queue_" + Queue.Writer.LocalName + "_can_write"; }
		}

		/// <summary>
		/// Get the (expected) name of the 'do_write' port on the hardware entity
		/// </summary>
		public string HardwareDoWritePortName
		{
			get { return "out_queue_" + Queue.Writer.LocalName + "_do_write"; }
		}

		/// <summary>
		/// Get the (expected) name of the 'data_in' port on the hardware entity
		/// </summary>
		public string HardwareDataInPortName
		{
			get { return "out_" + Queue.Writer.LocalName; }
		}
		
		private List<Weazel.Badger.Vhdl.Signal> readingEndDataSignals;
		public List<Weazel.Badger.Vhdl.Signal> ReadingEndDataSignals
		{
			get { return readingEndDataSignals; }
		}

		public override void InsertIntoUserLogicEntity(UserLogicEntity entity)
		{
			// call base method to insert common signals
			base.InsertIntoUserLogicEntity(entity);

			// insert the "segmented" reading end signals
			foreach (Weazel.Badger.Vhdl.Signal signal in readingEndDataSignals)
			{
				entity.AddDeclarativeItem(signal);
				entity.AddSignal(signal);
			}
		}

		private void initializeSignals()
		{
			System.Diagnostics.Trace.Assert(Queue != null);

			readingEndDataSignals = new List<Weazel.Badger.Vhdl.Signal>();
			for (int i = 0; i < AddressesNeeded; i++)
			{
				Weazel.Badger.Vhdl.Signal signal = 
					new Weazel.Badger.Vhdl.Signal(new StdLogicVector(32), null);

				readingEndDataSignals.Add(signal);
			}			
		}

		public HardwareToSoftwareQueue(
			MemoryMap memoryMap, 
			HardwareQueueRegistry queueRegistry, 
			Weazel.Willows.Model.Queue queue
		)
			: base(memoryMap, queueRegistry, queue)
		{
			initializeSignals();
		}


		private SignalAssignmentStatement getAssignPartialSignalExpression(
			StatementCollection collection, Signal result, Signal value)
		{
			System.Diagnostics.Trace.Assert(result.Type.TypeId == Types.StdLogicVector);
			System.Diagnostics.Trace.Assert(result.Type.Width == 32);
			System.Diagnostics.Trace.Assert(value.Type.TypeId == Types.StdLogicVector);

			SignalAssignmentStatement assignment =
				new SignalAssignmentStatement(result,
					new SignalReferenceExpression(
						collection,
						value),
					0,
					value.Type.Width - 1,
					true
				);

			return assignment;
		}

		public override void InsertQueue(UserLogicEntity entity)
		{
			base.InsertQueue(entity);

			if (AddressesNeeded > 1)
			{
				throw new NotImplementedException();
			}
			else
			{
				// simply connect the data out signal of the queue
				// with the one 'segmented' signal  

				SignalAssignmentStatement statement =
					getAssignPartialSignalExpression(
						entity.Statements,
						readingEndDataSignals[0],
						ReadingEndDataSignal
					);


					//new SignalAssignmentStatement(
					//  readingEndDataSignals[0],
					//  new SignalReferenceExpression(entity.Statements, ReadingEndDataSignal)
					//);

				entity.Statements.Add(statement);
			}
		}

		public override void SetupReadProcessCase(UserLogicEntity entity)
		{
			for (int i = 0; i < AddressesNeeded; i++)
			{
				Expression choiceExpression =
					GetChoiceExpression(
						entity.ReadProcessStatementCollection,
						GetRelativeAddressAsPositionalString(RelativeBaseAddress + i)
					);

				CaseStatementChoice choice =
					new CaseStatementChoice(choiceExpression);

				// data signal connected to address i
				Weazel.Badger.Vhdl.Signal dataSignal =
					ReadingEndDataSignals[i];

				// make process sensitive 
				entity.ReadProcess.AddSignalToSensivitityList(dataSignal);

				SignalAssignmentStatement assignDataOutSignal =
					new SignalAssignmentStatement(
					  entity.Ip2BusDataSignal,
						new SignalReferenceExpression(
							choice.Statements,
							dataSignal
						)
					);

				// When the final address of the data address range is
				// read the 'can_read' signal of the queue is set to acknowledge
				// the read.
				if (i == AddressCount - 1) // last data 32-bit signal
				{
					Weazel.Badger.Vhdl.Expressions.ConstantExpression one = 
						new Weazel.Badger.Vhdl.Expressions.ConstantExpression(
						choice.Statements,
						"1",
						new StdLogic(),
						null
					);

					SignalAssignmentStatement assignOneToDoRead =
						new SignalAssignmentStatement(
							ReadingEndDoReadSignal,
							one
						);

					choice.Statements.Add(assignOneToDoRead);

					// also insert a clear statement for the do read signal
					// BEFORE the case statement (at the top that is)
					ConstantExpression zero = 
						new ConstantExpression(
							choice.Statements,
							"'0'",
							new StdLogic(),
							null
						);

					SignalAssignmentStatement assignZeroToDoRead =
						new SignalAssignmentStatement(
							ReadingEndDoReadSignal,
							zero
						);

					 entity.ReadProcessStatementCollection.InsertFirst(assignZeroToDoRead);
				}

				 choice.Statements.Add(assignDataOutSignal);
				 entity.ReadProcessCaseStatement.AddChoice(choice);
			}
		}

		public override void SetupWriteProcessCase(UserLogicEntity entity)
		{
			// a hardware-to-software queue cannot be written by the bus
			for (int i = 0; i < AddressesNeeded; i++)
			{
				Expression choiceExpression =
					GetChoiceExpression(
						entity.WriteProcessOnClockStatementCollection, 
						GetRelativeAddressAsPositionalString(RelativeBaseAddress + i)
					);

				CaseStatementChoice choice = 
					new CaseStatementChoice(choiceExpression);

				choice.Statements.Add(new Comment("Cannot write hardware-to-software queue"));
				choice.Statements.Add(new BlackBoxStatement("null;"));
				entity.WriteProcessCaseStatement.AddChoice(choice);
			}
		}
	}
}
